//------------------------------------------------------------------------------
//
//  File: QuickLog.cpp
//
//  Copyright (C) 2008 Visual.Wei
//  Author: Visual.Wei <cpuwolf@sina.com>
//
//  quickly write debug message to a file
//
#include <windows.h>

#define MEM_LENGTH (512*1024)

#define DBG_LOG 1

typedef struct _DumpDebugMsg
{
	DWORD dwBufferValidLen;
	DWORD dwBufferLen;
	DWORD dwWriteBegin;
	CRITICAL_SECTION m_CSection;
	HANDLE hDThread;
	HANDLE m_hEndEv;
	BYTE buffer[1];
}DumpDebugMsg;


DumpDebugMsg * g_DumpBuffer;

static HANDLE hlogfile;

#define SOffset(type,member)  ( (UINT32)(&( ((type *)0) ->member)) ) 
#define csLOCK(h) EnterCriticalSection(&(h->m_CSection))
#define csUNLOCK(h) LeaveCriticalSection(&(h->m_CSection))
//------------------------------------------------------------------------------


static BOOL FileLogInit(LPCTSTR lpFileName)
{
	hlogfile=CreateFile(lpFileName,\
	                    GENERIC_WRITE,\
	                    FILE_SHARE_READ,\
	                    NULL,\
	                    CREATE_ALWAYS,\
	                    FILE_ATTRIBUTE_NORMAL,\
	                    NULL);
	if(hlogfile==INVALID_HANDLE_VALUE )
		return FALSE;
	return TRUE;
}

//--- write from the buffer beginning
static VOID MemInit() 
{
	DumpDebugMsg * header=g_DumpBuffer;	

	header->dwBufferLen=MEM_LENGTH-SOffset(DumpDebugMsg,buffer[0]);
	header->dwWriteBegin=0;
	header->dwBufferValidLen=0;
}

/* flush all memory to a file*/
static FlushMem2File()
{
	DWORD nwrite;
	DumpDebugMsg * header=g_DumpBuffer;	

	if(header->dwBufferValidLen>0)
	{
		RETAILMSG(DBG_LOG, (TEXT("quicklog: flush \n")));
		WriteFile(hlogfile,header->buffer,header->dwBufferValidLen,&nwrite,NULL);
		header->dwBufferValidLen=0;
		header->dwWriteBegin=0;
	}
	
	
}

//---just write to memory
static VOID WriteLog2Mem(void * buf, DWORD bufsize) 
{
	DumpDebugMsg * header=g_DumpBuffer;
	DWORD cacheleft;
	DWORD bufferleft=bufsize;
	
#if 1	
	static DWORD LongTime=0;
	DWORD time=GetTickCount();
#endif

	cacheleft=header->dwBufferLen-header->dwWriteBegin;
	while(bufferleft>0)
	{
		if(bufferleft<=cacheleft)
		{
			memcpy(header->buffer+header->dwWriteBegin,buf,bufferleft);
			header->dwWriteBegin+=bufferleft;
			header->dwBufferValidLen+=bufferleft;
			bufferleft=0;
		}
		else
		{
			memcpy(header->buffer+header->dwWriteBegin,buf,cacheleft);
			header->dwWriteBegin+=cacheleft;
			header->dwBufferValidLen+=cacheleft;
			bufferleft-=cacheleft;
		}
		//
		cacheleft=header->dwBufferLen-header->dwWriteBegin;
		// cache is full
		if(cacheleft==0)
		{
			//must flush memory
			FlushMem2File();
		}
	}
	
#if 1	
	if(GetTickCount()-time>LongTime)
	{
		LongTime=GetTickCount()-time;
		RETAILMSG(DBG_LOG, (TEXT("quicklog: longer %d \n"),LongTime));
	}
#endif

}

static CHAR *UnicodetoAnsi(LPCWSTR buffer,size_t size)
{
	CHAR *txt;
	size_t len;
	
	txt=(CHAR *)buffer;
	
	len=WideCharToMultiByte(CP_ACP,0,buffer,-1,txt,0,NULL,NULL);
	if(size*sizeof(WCHAR)>=len)
	{
		WideCharToMultiByte(CP_ACP,0,buffer,-1,txt,len,NULL,NULL);
	}
	return txt;
}

void WriteLogPrintkW(LPCTSTR format, ...)
{
	va_list pArgList;
	TCHAR buffer[128];
	size_t len;
	SYSTEMTIME time;
	CHAR *txt;
	
	//get local system time
	GetLocalTime(&time);
	wsprintf(buffer,_T("%02d:%02d:%02d "),\
		time.wHour,\
		time.wMinute,\
		time.wSecond);
	//--StringCchLength(buffer,128,&len);
	//Uicode to ANSI
	txt=UnicodetoAnsi(buffer,128);
	len=strlen(txt);
	//write log
	csLOCK(g_DumpBuffer);
	WriteLog2Mem(buffer,len);
	csUNLOCK(g_DumpBuffer);
			
	va_start(pArgList, format);
	StringCchVPrintf(buffer, 128,format, pArgList);
	va_end(pArgList);
	//--StringCchLength(buffer,128,&len);
	//Uicode to ANSI
	txt=UnicodetoAnsi(buffer,128);
	len=strlen(txt);
	//write log
	csLOCK(g_DumpBuffer);
	WriteLog2Mem(buffer,len);
	csUNLOCK(g_DumpBuffer);
}

void WriteLogPrintkA(LPCSTR format, ...)
{
	va_list pArgList;
	TCHAR buffer[128];
	size_t len;
	SYSTEMTIME time;
	CHAR *txt;
	
	//get local system time
	GetLocalTime(&time);
	wsprintf(buffer,_T("%02d:%02d:%02d "),\
		time.wHour,\
		time.wMinute,\
		time.wSecond);
	//--StringCchLength(buffer,128,&len);
	//Uicode to ANSI
	txt=UnicodetoAnsi(buffer,128);
	len=strlen(txt);
	//write log
	csLOCK(g_DumpBuffer);
	WriteLog2Mem(buffer,len);
	csUNLOCK(g_DumpBuffer);
			
	va_start(pArgList, format);
	StringCchVPrintfA((LPSTR)buffer, 128,format, pArgList);
	va_end(pArgList);
	//--StringCchLength(buffer,128,&len);
	//Uicode to ANSI
	//txt=UnicodetoAnsi(buffer,128);
	len=strlen((LPSTR)buffer);
	//write log
	csLOCK(g_DumpBuffer);
	WriteLog2Mem(buffer,len);
	csUNLOCK(g_DumpBuffer);
}

static void FileLogDeinit()
{
	CloseHandle(hlogfile);
}

static DWORD FlushThread(LPVOID lpParameter)
{
	DWORD    dwResult;
	DumpDebugMsg * head=(DumpDebugMsg *)lpParameter;
	
	while (1)
	{
		dwResult=WaitForSingleObject(head->m_hEndEv, 20*1000);
		switch(dwResult)
		{
			case(WAIT_TIMEOUT):
				csLOCK(head);
				FlushMem2File();
				csUNLOCK(head);
			break;
			case(WAIT_OBJECT_0):
			goto CleanUp;
		}
	}
CleanUp:	
	RETAILMSG(DBG_LOG, (TEXT("quicklog: Thread End \n")));
	csLOCK(head);
	FlushMem2File();
	csUNLOCK(head);
	return 0;
}

static BOOL AppInit()
{
	DWORD ThreadID;
	// Allocate enough storage for caching message
	g_DumpBuffer = (DumpDebugMsg *)LocalAlloc(LPTR, MEM_LENGTH);
	if (g_DumpBuffer == NULL)
	{
		ERRORMSG(DBG_LOG, (L"quicklog: Failed allocate DumpDebugMsg structure\r\n" ));
		return FALSE;
	}
	InitializeCriticalSection(&(g_DumpBuffer->m_CSection));
	MemInit();
	
	//create flush thread
	g_DumpBuffer->m_hEndEv = CreateEvent(NULL, FALSE, FALSE, NULL) ;
	if (NULL == g_DumpBuffer->m_hEndEv)
		return FALSE;
	RETAILMSG(DBG_LOG,(TEXT("quicklog::create flush thread\r\n")));
	g_DumpBuffer->hDThread = CreateThread(NULL, 0,FlushThread, (LPVOID)g_DumpBuffer, 0, &ThreadID);
	if (g_DumpBuffer->hDThread == NULL)
		return FALSE;
	CeSetThreadPriority(g_DumpBuffer->hDThread, 254);
	
	//create a empty log file
	FileLogInit(_T("\\WinQVPapp.log"));
	return TRUE;
}

static BOOL AppDeinit()
{
	if (g_DumpBuffer)
	{
		csLOCK(g_DumpBuffer);
		FlushMem2File();
		csUNLOCK(g_DumpBuffer);
		//release thread resource
		SetEvent(g_DumpBuffer->m_hEndEv);
		RETAILMSG(DBG_LOG,(TEXT("+quicklog::Wait thread\r\n")));
		WaitForSingleObject(g_DumpBuffer->hDThread, INFINITE);
		RETAILMSG(DBG_LOG,(TEXT("-quicklog::Wait thread\r\n")));
		CloseHandle(g_DumpBuffer->m_hEndEv);
		CloseHandle(g_DumpBuffer->hDThread);
		//
		DeleteCriticalSection(&(g_DumpBuffer->m_CSection));	
		LocalFree(g_DumpBuffer);
	}
	FileLogDeinit();
	
	return TRUE;
}
BOOL WINAPI DllMain(HINSTANCE  hInstance,ULONG      Reason,  LPVOID     pReserved)
{
    BOOL fRet = TRUE;

    if ( Reason == DLL_PROCESS_ATTACH ) 
    {
		RETAILMSG(DBG_LOG, (TEXT("quicklog: PROCESS_ATTACH \n")));
		DisableThreadLibraryCalls((HMODULE) hInstance);
		fRet=AppInit();
    }
    else if ( Reason == DLL_PROCESS_DETACH ) 
    {
		RETAILMSG(DBG_LOG, (TEXT("quicklog: PROCESS_DETACH \n")));
		fRet=AppDeinit();
    }

    return fRet;
}
